﻿using Xejen.Communication.Configuration;
using Xejen.Communication.Soap.Abstract;
using Xejen.Communication.Soap.Configuration;
using Microsoft.CSharp;
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Services.Description;

namespace Xejen.Communication.Soap
{
    /// <summary>
    /// 根据Soap协议构建客户端向远程服务器发起请求，实现技术采用ChannelFactory
    /// </summary>
    /// <see href="!:https://blog.csdn.net/anlun/article/details/44880545" langword="关于创建服务时怎么使用用户名和密码"/>
    /// <creator>marc</creator>
    public class WcfInvoker : ISoapInvoker<WcfAddress>
    {
        /// <summary>
        /// 通道工厂库
        /// </summary>
        private static readonly ConcurrentDictionary<Type, dynamic> _channelFactories = new ConcurrentDictionary<Type, dynamic>();

        /// <inheritdoc/>
        public SoapConfig Config { get; set; }

        /// <inheritdoc/>
        protected JsonSerializerOptions JsonSerializerOptions => Config.JsonSerializerOptions;

        /// <inheritdoc cref="WcfInvoker"/>
        protected WcfInvoker() { }

        /// <inheritdoc cref="WcfInvoker"/>
        public WcfInvoker(SoapConfig config)
        {
            Check.NotNull(config, nameof(config));

            Config = config;
        }

        /// <inheritdoc/>
        public virtual async Task<SoapResponse> InvokeServiceAsync(WcfAddress address, SoapConfig soapConfig = null, int timeout = -1, CancellationToken cancellationToken = default)
        {
            return await InvokeServiceAsync(address, new List<object>(), soapConfig, timeout, cancellationToken);
        }

        /// <inheritdoc/>
        public virtual async Task<SoapResponse> InvokeServiceAsync<TInput>(WcfAddress address, TInput input, SoapConfig soapConfig = null, int timeout = -1, CancellationToken cancellationToken = default)
        {
            if (!(input is IEnumerable enumerableInput))
            {
                throw new InvalidCastException($"输入参数必须是{nameof(IEnumerable)}的集合类型");
            }

            Type contractType = address.ServiceType;

            dynamic channelFactory = CreateChannelFactory(contractType, address, timeout);

            dynamic serviceInstance = channelFactory.CreateChannel();

            try
            {
                MethodInfo methodInfo = contractType.GetMethod(address.MethodName);

                if (methodInfo != null)
                {
                    object[] parameters = enumerableInput.Cast<object>().ToArray();

                    var task = Task.Run(() => methodInfo.Invoke(serviceInstance, parameters), cancellationToken);

                    if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                        object result = await task.ConfigureAwait(false);
                        return SoapResponse.Success(result);
                    }
                    else
                    {
                        throw new TimeoutException("方法<InvokeServiceAsync>：服务调用超时.");
                    }
                }
                else
                {
                    throw new ArgumentException($"Method {address.MethodName} not found on service.");
                }
            }
            finally
            {
                if (serviceInstance is ICommunicationObject communicationObject)
                {
                    try
                    {
                        communicationObject.Close();
                    }
                    catch
                    {
                        communicationObject.Abort();
                    }
                }
            }
        }

        /// <summary>
        /// 创建通道工厂
        /// </summary>
        /// <param name="contractType">通道类型</param>
        /// <param name="address">地址</param>
        /// <param name="timeout">超时时间</param>
        /// <returns></returns>
        private dynamic CreateChannelFactory(Type contractType, WcfAddress address, int timeout)
        {
            dynamic channelFactory = _channelFactories.GetOrAdd(contractType, new Func<Type, dynamic>((type) =>
            {
                BasicHttpBinding binding = new BasicHttpBinding();
                binding.SendTimeout = TimeSpan.FromMilliseconds(timeout);

                EndpointAddress endpointAddress = new EndpointAddress(address.RequestUri);

                Type channelFactoryType = typeof(ChannelFactory<>).MakeGenericType(type);
                channelFactory = Activator.CreateInstance(channelFactoryType, binding, endpointAddress);

                if (!string.IsNullOrEmpty(Config.UserName))
                {
                    channelFactory.Credentials.UserName.UserName = Config.UserName;
                    channelFactory.Credentials.UserName.Password = Config.Password;
                }

                return channelFactory;
            }));

            return channelFactory;
        }
    }
}
